package top.archiesean.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang.WordUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import top.archiesean.constants.Constants;
import top.archiesean.mapper.CommonMapper;
import top.archiesean.pojo.ColumnEntity;
import top.archiesean.pojo.TableEntity;
import top.archiesean.properties.GenerateProperties;
import top.archiesean.vo.ColumnVo;
import top.archiesean.vo.TableVo;

import java.io.File;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author ArchieSean
 * @description 自动代码生成工具
 * @date 2024-04-19
 */
@Service
@RequiredArgsConstructor
public class GenerateServer {
    private final CommonMapper commonMapper;

    private final GenerateProperties properties;

    /**
     * 根据表名称逆向生成
     *
     * @param tableName 表名称
     * @return byte[]
     */
    public byte[] getCode(String tableName) {
        //根据表名称获取表信息
        List<TableVo> tables = commonMapper.tablesQuery(tableName);
        //根据表名称获取列信息
        List<ColumnVo> columns = commonMapper.tableColumnQuery(tableName);

        ByteOutputStream outputStream = new ByteOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        //根据表名称分组
        Map<String, List<ColumnVo>> map = columns.stream().collect(Collectors.groupingBy(ColumnVo::getTableName));

        for (TableVo table : tables) {
            doGenerateCode(table, map.get(table.getTableName()), zip);
        }
        IoUtil.close(zip);
        return outputStream.toByteArray();
    }

    /**
     * 构建逆向代码生成
     *
     * @param table          表信息
     * @param columnEntities 列信息
     * @param zip            输出流
     */
    private void doGenerateCode(TableVo table, List<ColumnVo> columnEntities, ZipOutputStream zip) {
        TableEntity tableEntity = new TableEntity();
        tableEntity.setTableName(table.getTableName());
        tableEntity.setComments(table.getTableComment());
        setTableName(tableEntity);
        boolean hasBigDecimal = false;
        tableEntity.setColumns(setTableColumns(columnEntities, tableEntity, hasBigDecimal));
        //没有主键时，设置第一个字段为主键
        if (ObjectUtils.isEmpty(tableEntity.getPk())) {
            tableEntity.setPk(tableEntity.getColumns().get(0));
        }

        Map<String, Object> map = initVelocity(tableEntity, hasBigDecimal);
        VelocityContext context = new VelocityContext(map);
        Set<String> templates = Constants.getTemplates();

        templates.forEach(template -> {
            //渲染模版
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, CharsetUtil.UTF_8);
            tpl.merge(context, sw);
            try {
                //添加到zip
                zip.putNextEntry(new ZipEntry(
                        Objects.requireNonNull(
                                getFileName(template, tableEntity.getCaseClassName(), map.get("package").toString())
                        )
                ));
                IoUtil.write(zip, StandardCharsets.UTF_8, false, sw.toString());
                IoUtil.close(sw);
                zip.closeEntry();
            } catch (Exception ex) {
                throw new RuntimeException("渲染模板失败，表名：" + tableEntity.getTableName(), ex);
            }
        });


    }

    private void setTableName(TableEntity tableEntity) {
        // 表名修改为类名
        String className = tableEntity.getTableName().replaceFirst(properties.getTablePrefix(), "");
        className = WordUtils.capitalizeFully(className, new char[]{'_'}).replace("_", "");
        tableEntity.setCaseClassName(className);
        tableEntity.setLowerClassName(StrUtil.lowerFirst(className));
    }


    private List<ColumnEntity> setTableColumns(List<ColumnVo> columnEntities,
                                               TableEntity tableEntity, boolean hasBigDecimal) {
        List<ColumnEntity> columnList = Lists.newArrayList();

        for (ColumnVo column : columnEntities) {
            ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setTableName(tableEntity.getTableName());
            columnEntity.setColumnName(column.getColumnName());
            columnEntity.setDataType(column.getDataType());
            columnEntity.setComments(column.getColumnComment());
            columnEntity.setExtra(column.getExtra());

            //列名转换成Java属性名
            String attrName =
                    WordUtils.capitalizeFully(columnEntity.getColumnName(), new char[]{'_'}).replace("_", "");
            columnEntity.setCaseAttrName(attrName);
            columnEntity.setLowerAttrName(StrUtil.lowerFirst(attrName));

            //列的数据类型，转换成Java类型
            String attrType = Constants.JAVA_DATATYPE.get(columnEntity.getDataType());
            columnEntity.setAttrType(attrType);
            String jdbcType = Constants.JDBC_DATATYPE.get(columnEntity.getDataType());
            columnEntity.setJdbcType(jdbcType);
            if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
                hasBigDecimal = true;
            }
            //是否主键
            if ("PRI".equalsIgnoreCase(column.getColumnKey()) && tableEntity.getPk() == null) {
                tableEntity.setPk(columnEntity);
            }

            columnList.add(columnEntity);
        }
        return columnList;
    }

    private Map<String, Object> initVelocity(TableEntity tableEntity, boolean hasBigDecimal) {
        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);
        //封装模板数据
        Map<String, Object> map = Maps.newHashMap();
        map.put("tableName", tableEntity.getTableName());
        map.put("pk", tableEntity.getPk());
        map.put("className", tableEntity.getCaseClassName());
        map.put("classname", tableEntity.getLowerClassName());
        map.put("pathName", tableEntity.getLowerClassName().toLowerCase());
        map.put("columns", tableEntity.getColumns());
        map.put("hasBigDecimal", hasBigDecimal);
        map.put("datetime", DateUtil.now());
        map.put("year", DateUtil.year(new Date()));
        map.put("comments", tableEntity.getComments());
        map.put("author", "generate");
        map.put("package", properties.getPackagePath());

        return map;
    }

    /**
     * 获取文件名
     *
     * @param template    模板类型
     * @param className   类名称
     * @param packageName 包名
     * @return Str 获取文件名
     */
    private String getFileName(String template, String className, String packageName) {
        // 包路径
        String packagePath = Constants.COMMON_PATH + File.separator + "src" + File.separator + "main"
                + File.separator + "java" + File.separator;
        // 资源路径
        String resourcePath = Constants.COMMON_PATH + File.separator + "src" + File.separator + "main"
                + File.separator + "resources" + File.separator;


        if (StrUtil.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator;
        }
        //pojo
        if (template.contains(Constants.ENTITY_JAVA_VM)) {
            return packagePath + File.separator + "pojo" + File.separator + className + ".java";
        }
        // mapper
        if (template.contains(Constants.MAPPER_JAVA_VM)) {
            return packagePath + File.separator + "mapper" + File.separator + className + "Mapper.java";
        }
        //iService
        if (template.contains(Constants.SERVICE_BIZ_JAVA_VM)) {
            return packagePath + File.separator + "service" + File.separator + "I" + className + ".java";
        }
        //serviceImpl
        if (template.contains(Constants.SERVICE_IMPL_JAVA_VM)) {
            return packagePath + File.separator + "service" + File.separator + "impl" + File.separator + "I" + className + ".java";
        }
        // controller
        if (template.contains(Constants.CONTROLLER_JAVA_VM)) {
            return packagePath + File.separator + "controller" + File.separator + className + "Controller.java";
        }
        // mapper xml
        if (template.contains(Constants.MAPPER_XML_VM)) {
            return resourcePath + "mapper" + File.separator + className + "Mapper.xml";
        }
        return null;
    }
}
